# -*- coding: utf-8 -*-
# Copyright (c) 2021, Fisher and contributors
# For license information, please see license.txt

from __future__ import unicode_literals
from datetime import date, datetime, timedelta
import frappe
from frappe import _
from frappe.model.naming import getseries
from frappe.model.document import Document
from zelin_am.utils import *
from frappe.query_builder.functions import Count,Sum, Cast
from pypika import Case, Criterion
from frappe.utils import flt, cstr, nowdate, nowtime,rounded, get_user_date_format, add_days, today, now
from pprint import pprint

class MultiLevelMRP(Document):    
    def initialize(self):        
        self.plan_order_id = 0
        self.mrp_inventory_id = 0
        self.warehouses = frappe.get_all('Warehouse', filters={'company': self.company, 'mrp_exclude':0}, pluck='name')
        self.holiday_list = frappe.get_cached_value('Company',  self.company,  "default_holiday_list")
        self.plan_days_map = frappe._dict()
        self.item_plan_master_map = get_item_plan_master_map(self.company)
        self.bom_map = get_bom_map(self.company)
        self.item_code_onhand_map = get_item_code_onhand_map(self.company)
        self.plan_orders = []
        self.plan_order_items = []
        self.logs = []

    def get_item_code_onhand(self, item_code):
        qty_dict = self.item_code_onhand_map.get(item_code)
        return qty_dict and qty_dict.onhand or 0

    def log(self, step, message):
        print(step, message)
        if not hasattr(self, 'logs'):
            self.logs = []
        self.logs.append([self.name,"Multi Level MRP", "mrp_run_log",_(step), nowtime(), _(message)])

    def _mrp_cleanup(self):
        self.log('_mrp_cleanup', "Start MRP Cleanup")
        filter = {'mrp': self.name}        
        frappe.db.delete("MRP Plan Item",{'parent': self.name})
        frappe.db.delete("MRP Run Log",{'parent': self.name})
        self.mrp_run_log = []
        sql = """delete pi from `tabPlan Order Item` pi 
                    inner join `tabPlan Order` p on pi.parent=p.name
                    inner join `tabMulti Level MRP` m on p.mrp=m.name 
                    where m.docstatus<>1  and is_fixed = 0"""
        sql1 = """delete p from `tabPlan Order` p inner join `tabMulti Level MRP` m on p.mrp=m.name where m.docstatus<>1  and is_fixed = 0"""
        sql2 = """delete p from `tabMRP Inventory` p inner join `tabMulti Level MRP` m on p.mrp=m.name where m.docstatus<>1"""
        for s in [sql, sql1,sql2]:
            frappe.db.sql(s)
        self.log('_mrp_cleanup', "End MRP Cleanup")
        return True

    def _low_level_code_calculation(self):
        self.log("_low_level_code_calculation","Start low level code calculation")
        counter = 999999
        llc = 0
        filter = {'company': self.company}
        frappe.db.set_value('Item Default', filter,{"llc": llc})
        filter.update({"llc":llc})        
        products = frappe.db.get_values('Item Default', filter)
        if products:
            counter = len(products)
        log_msg = _("Low level code 0 finished - Nbr. products: {0}").format(counter)        
        self.log("_low_level_code_calculation",log_msg)

        sql = """select BI.item_code from `tabBOM` B inner join `tabBOM Item` BI on B.name = BI.parent 
                    inner join `tabItem Default` header_item on B.item = header_item.parent and
                                header_item.company = B.company
                    inner join `tabItem Default` child_item on BI.item_code = child_item.parent and
                                child_item.company = B.company                                
                    where B.docstatus=1 and B.is_default=1  and 
                        child_item.llc = %s and header_item.name in %s"""
        while counter:
            llc += 1
            print('llc=%s' % llc)
            filter.update({"llc":llc - 1})
            products = frappe.db.get_values('Item Default', filter)            
            if products:        
                bom_lines = frappe.db.sql(sql, (llc - 1, [p[0] for p in products]))
                if bom_lines:
                    frappe.db.set_value('Item Default',
                        {'company': self.company, 'parent':('in',[b[0] for b in bom_lines])},
                        {"llc": llc})
            filter.update({"llc":llc})            
            counter = frappe.db.count('Item Default', filter)
            log_msg = _("Low level code {0} finished - Nbr. products: {1}").format(llc, counter)            
            self.log("_low_level_code_calculation",log_msg)            

        mrp_lowest_llc = llc
        self.log("_low_level_code_calculation","End low level code calculation")        
        return mrp_lowest_llc

    @frappe.whitelist()
    def get_plan_items(self, save_db = False):
        self.plan_items = []
        if not hasattr(self, 'warehouses'):
            self.initialize()
            self._mrp_cleanup()
        self.log('get plan items', 'Start get plan items')
        #先根据筛选条件获取销售订单及其相关物料号
        filters = frappe._dict({})
        for f in ['sales_order','item_code','from_delivery_date', 'to_delivery_date']:
            filter_value = self.get(f)
            if filter_value:
                filters[f] = filter_value
        self.plan_items = get_open_so(self.warehouses, filters = filters, sort_key = 21)
        item_code_list = []
        if filters:
            if not self.plan_items: return
            unique_items = {p.item_code for p in self.plan_items}
            for item_code in unique_items:
                item_code_list.append(item_code)
                get_bom_sub_items(item_code, self.company, item_code_list)

        fn = (
            #(get_open_so,21),
            (get_open_wo_gi,22),
            (get_open_mr_gi,23),
            (get_open_subcontract_gi,24),
            (get_open_wo,11),
            (get_open_po,12),
            (get_open_mr,13)
        )        
                
        for (f, sort_key) in fn:
            data = f(self.warehouses, item_code_list = item_code_list, sort_key = sort_key)
            if data: self.plan_items.extend(data)
        self.plan_items.extend(get_open_plan_order(self.company, item_code_list = item_code_list, sort_key = 14))
        self.plan_items.extend(get_open_plan_order_items(self.company, item_code_list = item_code_list, sort_key = 25))
        self.plan_items = sorted(self.plan_items, key=lambda k: (k['item_code'], k['due_date'], k['sort_key']))

        self.items = []
        self.extend('items', self.plan_items)    
        self.log('get plan items', 'End get plan items')

    def _prepare_plan_item_data_bom_explosion(self, item_plan_master_doc, bomline, qty, mrp_date_demand_2, name, plan_order = None):
        key = item_plan_master_doc.item_code                  
        return frappe._dict({
            "parent" : plan_order,
            "parenttype": "Plan Order",
            "parentfield": "items",
            "item_code": bomline.item_code,
            "item_name": bomline.item_name,
            "qty": -bomline.stock_qty  / bomline.quantity * qty,  # 单用量乘以订单需求量
            "stock_uom":bomline.stock_uom,
            "ref_doctype": "BOM",
            "ref_docname": bomline.bom_no,
            "due_date": mrp_date_demand_2,            
            "parent_item_code": bomline.name,       #来自bom map中bom header的item 字段
            "note": _("Demand Bom Explosion: {0}").format(name or item_plan_master_doc.item_code),
            "sort_key": 25
        })    
    
    def explode_action(self, item_plan_master_doc, mrp_action_date, name, qty, plan_order = None):
        """Explode requirements."""
        mrp_date_demand = mrp_action_date
        if mrp_date_demand < date.today():
            mrp_date_demand = date.today()
        item_code = item_plan_master_doc.item_code

        for bomline in self.bom_map[item_code]:
            if bomline.stock_qty <= 0.00:   # or bomline.item_code.type != "product":
                continue
            #if self._exclude_from_mrp(bomline.item_code, item_plan_master_doc.plant,mrp_explosion=True):
                # Stop explosion.
            #    continue
            #if bomline._skip_bom_line(item_plant_id.item_code): dummy component
            #    continue
            # TODO: review: mrp_transit_time, mrp_inspection_time
            mrp_date_demand_2 = mrp_date_demand - timedelta(
                days=((item_plan_master_doc.lead_time_days or 0) + (item_plan_master_doc.inspection_time or 0)))
            plan_item = self._prepare_plan_item_data_bom_explosion(item_plan_master_doc, bomline, qty, mrp_date_demand_2, name, plan_order = plan_order)
            #todo 应该添加到计划订单明细(相关需求)中去
            self.plan_order_items.append(plan_item)
        return True    
    
    def _exclude_from_mrp(self, item_code, mrp_explosion = 0):
        """ To extend with various logic where needed. """                                 
        return False      
    
    def _init_plan_item_grouped_demand(self, nbr_create, item_plan_master_doc):
        last_date = None
        last_qty = 0.00
        item_code = item_plan_master_doc.item_code
        safety_stock = item_plan_master_doc.safety_stock or 0
        onhand = self.get_item_code_onhand(item_code)
        grouping_delta = item_plan_master_doc.consolidate_demand_days or 0
        move_list = [p for p in self.plan_items if p.item_code == item_code]
        for move in move_list: 
            mrp_date, mrp_qty = (move.due_date, move.qty)           
            if self._exclude_move(move):
                continue
            if (
                last_date
                and (mrp_date >= last_date + timedelta(days=grouping_delta))
                and ( (onhand + last_qty + mrp_qty) < safety_stock
                    or (onhand + last_qty) < safety_stock
                )
            ):
                note = _("Grouped Demand for {0} Days").format(grouping_delta)
                qtytoorder = safety_stock - onhand - last_qty
                cm = self.create_action(
                    item_plan_master_doc=item_plan_master_doc,
                    mrp_date=last_date,
                    mrp_qty=qtytoorder,
                    #ref_docname=note,
                    note = note
                )
                qty_ordered = cm.get("qty_ordered", 0.0)
                onhand = onhand + last_qty + qty_ordered
                last_date = None
                last_qty = 0.00
                nbr_create += 1
            if (
                (onhand + last_qty + mrp_qty) < safety_stock
                or (onhand + last_qty) < safety_stock
            ):
                if not last_date or last_qty == 0.0:
                    last_date = mrp_date
                    last_qty = mrp_qty
                else:
                    last_qty += mrp_qty
            else:
                last_date = mrp_date
                onhand += mrp_qty

        if last_date and last_qty != 0.00:
            note = _("Grouped Demand for {0} Days").format(grouping_delta)
            qtytoorder = safety_stock - onhand - last_qty
            cm = self.create_action(item_plan_master_doc=item_plan_master_doc, 
                mrp_date=last_date,
                mrp_qty=qtytoorder,
                note=note)
            qty_ordered = cm.get("qty_ordered", 0.0)
            onhand += qty_ordered
            nbr_create += 1
        return nbr_create
    
    def _exclude_move(self, move):
        """Improve extensibility being able to exclude special moves."""
        return False

    def _mrp_calculation(self, mrp_lowest_llc):
        self.log("_mrp_calculation", "Start MRP calculation")
        year_last_2 = str(datetime.today().year)[-2:]
        self.year_last_2 = year_last_2
        self.mrp_id = getseries(f'PL{year_last_2}', 4)        
        counter = 0            
        llc = 0
        #self.plan_items包括销售订单与在途， self.plan_order_items包括计划订单中的相关需求
        #plan_items = self.plan_items + self.plan_order_items
        #plan_items = sorted(plan_items, key=lambda k: (k['item_code'], k['due_date'], k['sort_key']))
        while mrp_lowest_llc > llc:
            item_plan_master_list =[item for item in self.item_plan_master_map.values() if item.llc == llc] 
            llc += 1
            for item_plan_master_doc in item_plan_master_list:
                item_code, consolidate_demand_days = (item_plan_master_doc.item_code,item_plan_master_doc.consolidate_demand_days)
                safety_stock = item_plan_master_doc.safety_stock or 0
                nbr_create = 0
                onhand = 0
                if not item_plan_master_doc.ignore_existing_stock:
                    onhand = self.get_item_code_onhand(item_code)
                if not consolidate_demand_days or consolidate_demand_days == 0:
                    #todo 需要按日期，供应、需求排序
                    move_list = [r for r in self.plan_items + self.plan_order_items if r.item_code == item_code]
                    move_list = sorted(move_list, key=lambda k: (k['due_date'], k['sort_key'],k['ref_docname']))
                    for move in move_list:
                        #print('move =', move)                            
                        if self._exclude_move(move):
                            continue
                        qtytoorder = safety_stock - onhand - move.qty
                        print('qtytoorder=', qtytoorder, onhand, move.qty)                            
                        if qtytoorder > 0.0:
                            qtytoorder = self.adjust_qty(qtytoorder, item_plan_master_doc)
                            cm = self.create_action(item_plan_master_doc=item_plan_master_doc, mrp_date=move.due_date,
                                mrp_qty=qtytoorder,
                                ref_doctype = move.ref_doctype,
                                ref_docname=move.ref_docname,
                                ref_doc_item = move.ref_doc_item,
                                note = move.note,
                            )
                            qty_ordered = cm["qty_ordered"]
                            onhand += move.qty + qty_ordered
                            nbr_create += 1
                        else:
                            onhand += move.qty
                        #这里要记录一个需求，由哪些供应 库存，在途，计划满足了    
                else:
                    nbr_create = self._init_plan_item_grouped_demand(nbr_create, item_plan_master_doc)

                if onhand < safety_stock and nbr_create == 0:
                    qtytoorder = item_plan_master_doc.safety_stock - onhand
                    if qtytoorder > 0:
                        qtytoorder = self.adjust_qty(qtytoorder, item_plan_master_doc)
                    cm = self.create_action(
                        item_plan_master_doc=item_plan_master_doc,
                        mrp_date=date.today(),
                        mrp_qty=qtytoorder,
                        note="Safety Stock"
                    )
                    qty_ordered = cm["qty_ordered"]
                    onhand += qty_ordered
                counter += 1

        log_msg = _("MRP Calculation LLC {0} Finished - Nbr. products: {1}").format(llc - 1, counter)
        self.log("_mrp_calculation",log_msg)

        self.log("_mrp_calculation","End MRP calculation")

    def adjust_qty(self, qty, plan_master):
        qty_multiple, min_order_qty, max_order_qty = (plan_master.qty_multiple or 0, plan_master.min_order_qty or 0, plan_master.max_order_qty or 0)
        if min_order_qty and qty < min_order_qty:
            qty = min_order_qty
        if qty_multiple:
            Quotient = qty / qty_multiple
            rounded_quotient = frappe.utils.floor(Quotient)
            if Quotient > rounded_quotient:
                qty = (rounded_quotient + 1) * qty_multiple
        if max_order_qty and qty > max_order_qty:
            qty = max_order_qty        
        return qty

    def create_action(self, item_plan_master_doc, mrp_date, mrp_qty, ref_doctype = None, ref_docname = None, ref_doc_item = None, note=None,values=None):
        if not values:
            values = {}
        action_date, date_supply = self._get_action_and_supply_dates(item_plan_master_doc, mrp_date)
        return self.create_plan_order(item_plan_master_doc, mrp_qty,
                 ref_doctype, ref_docname, ref_doc_item, note,
                 date_supply, action_date, values=values)

    def _get_action_and_supply_dates(self, item_plan_master_doc, mrp_date):
        mrp_date_supply = date.today() if mrp_date < date.today() else mrp_date
        lead_time = item_plan_master_doc.lead_time_days or 0        
        mrp_action_date = self.plan_days(mrp_date, -1 * lead_time)
        return mrp_action_date, mrp_date_supply              

    def plan_days(self, date, days):
        schedule_date = self.plan_days_map.get((date, days))
        if not schedule_date:
            schedule_date = add_days(date, days)            
            if self.holiday_list:
                holidays = frappe.db.sql_list('''select holiday_date from `tabHoliday` where parent=%s''', holiday_list)    
                for i in range(abs(days)):
                    if schedule_date in holidays:
                        schedule_date = add_days(schedule_date, 1 if days>0 else -1)
            self.plan_days_map[(date, days)] = schedule_date                           
        return schedule_date
    
    def create_plan_order(self,item_plan_master_doc,mrp_qty,ref_doctype, ref_docname, ref_doc_item, note, mrp_date_supply,mrp_action_date,values=None):        
        if self._exclude_from_mrp(item_plan_master_doc.item_code, item_plan_master_doc.plant):
            values["qty_ordered"] = 0.0
            return values

        qty_ordered = values.get("qty_ordered", 0.0) if values else 0.0
        qty_to_order = mrp_qty
        while qty_ordered < mrp_qty:
            qty = self.adjust_qty(qty_to_order, item_plan_master_doc)
            order_data = self._prepare_plan_order_data(
                item_plan_master_doc, qty_to_order, qty, mrp_date_supply, mrp_action_date, ref_doctype, ref_docname, ref_doc_item, note
            )
            qty_to_order -= qty
            #plan_order = frappe.get_doc(order_data).insert()
            self.plan_orders.append(order_data)
            qty_ordered = qty_ordered + qty

            if (item_plan_master_doc.supply_method in ['Manufacture','Subcontract'] and 
                item_plan_master_doc.item_code in self.bom_map): 
                self.explode_action(item_plan_master_doc, mrp_action_date, ref_docname, qty, plan_order = order_data.name)

        values["qty_ordered"] = qty_ordered
        log_msg = _("{0}: qty_ordered = {1}").format(
            item_plan_master_doc.item_code
            or item_plan_master_doc.item_code,
            qty_ordered,
        )
        self.log("create_plan_order", log_msg)
        return values

    def _prepare_plan_order_data(self, item_plan_master_doc, qty_to_order, qty, mrp_date_supply, mrp_action_date, ref_doctype, ref_docname, ref_doc_item, note=None):
        self.plan_order_id += 1
        bom_doc = self.bom_map.get(item_plan_master_doc.item_code)
        bom_no = bom_doc and bom_doc[0].bom_no or ''
        if ref_doctype == "Purchase Order" and item_plan_master_doc.supply_method == 'Subcontract':
            is_subcontracted = 1
        else:
            is_subcontracted = 0
        return frappe._dict({
            #PL2100010000,PL前缀，21 年后两位   0001 MR运行序号 0001 计划订单序号
            "name": f"PL{self.year_last_2}{self.mrp_id}{str(self.plan_order_id).zfill(4)}",
            "item_code": item_plan_master_doc.item_code,
            "item_name":  item_plan_master_doc.item_name,
            "company":self.company,
            "qty": qty,
            "stock_uom":item_plan_master_doc.stock_uom,
            "due_date": mrp_date_supply,
            "order_release_date": mrp_action_date,
            "supply_method": item_plan_master_doc.supply_method,
            "supplier":item_plan_master_doc.default_supplier,
            "bom": bom_no,
            "qty_released": 0.0,
            "ref_doctype": ref_doctype,
            "is_subcontracted": is_subcontracted,
            "ref_docname": ref_docname,
            "ref_doc_item": ref_doc_item,
            "required_qty": qty_to_order,
            "note": note or _("Planned supply for: {0}").format(ref_docname),
            "is_fixed": False
            #"doctype":"Plan Order"
        })

    def get_mrp_inventory_data(self):      
        doc_list = [frappe._dict(
                        dict(
                            origin = 'Plan Item',
                            item_code = p.item_code,
                            company = self.company,
                            mrp_date= p.due_date,
                            qty = p.qty)
                        ) 
                    for p in self.plan_items
                ]
        doc_list.extend([frappe._dict(
                            dict(
                                origin = 'Plan Order',
                                item_code = p.item_code,
                                company = p.company,
                                mrp_date= p.due_date,
                                qty = p.qty
                                )
                            ) 
                        for p in self.plan_orders
                    ])
        doc_list = sorted(doc_list, key=lambda k: (k['item_code'], k['mrp_date']))
        return doc_list

    def _mrp_final_process(self):
        self.log("_mrp_final_process", "Start MRP final process")  
        doc_list = self.get_mrp_inventory_data()
        mrp_inventory_list = []
        last_item_code, last_mrp_date, last_running_availability = None, None, 0
        for doc in doc_list:
            item_code, mrp_date, qty = (doc.item_code, doc.mrp_date, doc.qty)            
            supply_qty, demand_qty, to_procure = (0,0,0)
            if doc.origin == 'Plan Order':
                to_procure = qty
            else:
                supply_qty = qty if qty > 0 else 0
                demand_qty = (qty * -1) if qty < 0 else 0
            if item_code != last_item_code or mrp_date != last_mrp_date:    
                row = frappe._dict({
                        'item_code' : doc.item_code,
                        'company': doc.company,
                        'mrp_date': doc.mrp_date,
                        'mrp': self.name,
                        'supply_qty': supply_qty,
                        'demand_qty': demand_qty,
                        'to_procure': to_procure
                    })
                mrp_inventory_list.append(row)    
                if item_code != last_item_code:  #新物料，取初始库存
                    row['initial_on_hand_qty'] = self.get_item_code_onhand(item_code)
                elif mrp_date != last_mrp_date:  #日期不同，初始数量带上一行
                    row['initial_on_hand_qty'] = running_availability    
            else:
                row['supply_qty'] += supply_qty
                row['demand_qty'] += demand_qty
                row['to_procure'] += to_procure
            running_availability = row['initial_on_hand_qty'] + row['supply_qty'] - row['demand_qty'] + row['to_procure']
            row['running_availability'] = running_availability   
            last_item_code, last_mrp_date = (item_code, mrp_date)                    
        self.log("_mrp_final_process","End MRP final process")
        self.save_to_database(mrp_inventory_list)
        #计划订单关联到库存 
        sql = """update `tabPlan Order` po inner join `tabMRP Inventory` mi on
                    po.due_date = mi.mrp_date and po.item_code= mi.item_code and 
                    po.company=mi.company and po.mrp = mi.mrp 
                    set po.mrp_inventory = mi.name"""
        frappe.db.sql(sql)

    def save_to_database(self, mrp_inventory_list = []):
        if mrp_inventory_list:                
            insert_multi('MRP Inventory', mrp_inventory_list)

        fields =  ["parent", "parenttype", "parentfield",  "ref_doctype", "is_subcontracted","ref_docname", "ref_doc_item",
            "item_code", "parent_item_code", "qty", "uom", "stock_uom", "conversion_factor",
            "item_name",  "partner_type",  "partner", "due_date", "note","sort_key"                
            ]
        #conversion_factor是浮点数字段，数据库不接受空值，设个默认值1
        plan_items = [dict(r, **{'parent': self.name,
                                'parenttype':"Multi Level MRP",
                                'parentfield':"items",
                                'conversion_factor': r.get('conversion_factor') or 1,
                                'is_subcontracted': r.get('is_subcontracted') or 0
                                }
                        )
                    for r in self.plan_items]
        insert_multi('MRP Plan Item', plan_items, fields, add_idx = True)
        #与MRP Plan Item表结构一致，parent,parenttype,parentfield已在产生记录时赋值了
        plan_order_items = [dict(r, **{'conversion_factor': r.get('conversion_factor') or 1})
                    for r in self.plan_order_items]
        fields.remove('is_subcontracted')   #计划明细表无此字段                
        insert_multi('Plan Order Item', plan_order_items, fields, add_idx = True)

        fields =  ["name", "company","item_code", "qty","uom","stock_uom","item_name","due_date","order_release_date",
            "mrp_action","supply_method", "supplier", "note","mrp"]          
                           
        plan_orders = [dict(r, **{'mrp': self.name}) for r in self.plan_orders]
        insert_multi('Plan Order', plan_orders) #, fields
        fields = ["parent","parenttype","parentfield","step","time","message"]
        insert_multi("MRP Run Log", self.logs, fields, add_idx = True)


@frappe.whitelist()
def run_mrp_multi_level(mrp_doc_name):
    """1.初始化公用变量,获取物料计划基础数据，物料清单，货源清单等基础数据
        2.删除MRP库存，未打锁定标记的计划订单，MRP待计划明细，MRP运行日志
        3.基于物料清单计算全部物料的低层码，用于按层级顺序（即最终成品、半成品、原材料)逐级进行计划运算
        4.获取待计划物料信息(单据类型，单据号，行号，物料号，数量，单位，日期，上层物料号)
        4.1 需求： 
            4.1.1 已提交未关闭且有剩余出库数量的销售订单，物料需求(预测)
            4.1.2 在途订单：
                4.1.2.1 委外采购订单原材料剩余发料数量
                4.1.2.2 生产工单剩余原材料剩余发料数量
                4.1.2.3 已锁定的计划订单部件(相关)需求,即上次MRP运算后已打锁定标记(已确认待转工单的计划订单)
            4.1.3 物料安全库存    
        4.2 供应：
            4.2.1 实际库存
            4.2.2 在途订单
                4.2.2.1 已提交未关闭且有剩余入库数量的采购订单(包括委外)
                4.2.2.2 生产工单(成品料号)
        5.计划运算:按成品、半成品、原材料逐层(低层码)计算
            合并物料的需求与供应项，按日期升序，排序码(供应在先，需求在后)
            5.1 计算需求
                5.1.1 毛需求 = 安全库存 + 销售订单需求 + 工单/委外采购订单下层物料需求(相关需求)
                5.1.2 净需求 = 毛需求 - 供应(实际可分配库存) - 供应(在途工单、采购订单)
                5.1.3 (如果净需求 > 0) 净需求调整为大于等于最小订单量、最小包量的倍数，不超过最大订单量(即拆单)
            5.2 生成计划订单(建议)
                5.2.1 对供应方式为采购的原材料
                    5.2.1 考虑供应商配额 
                5.2.2 针对有默认物料清单的成品或半成品展物料清单，触发下层物料相关需求
                    5.2.2.1 基于交货提前期计算下层物料到期日
            5.3 针对缺料数量生成计划订单
        6.按物料号、日期生成MRP 库存表，即滚动当时期初库存，需求、供应、计划、可用数量表
        7.将计算结果：MRP待计划物料，计划订单及明细，MRP库存以及运行日志写入数据库
    """
    self = frappe.get_doc('Multi Level MRP', mrp_doc_name)    
    self.initialize()
    self._mrp_cleanup()
    mrp_lowest_llc = self._low_level_code_calculation()
    if not self.items: #如果网页界面获取过待计划物料并修改过参数后，直接使用。
        self.get_plan_items()
    else:
        #如果作为doc method,js会将doc作为dict传回python,此时会导致items里的due_date字段变成文本类型，与其它日期比较时出现 
        #文本与日期比较的错误，使用单独的方法，重新从数据库读取doc内容。
        self.plan_items = [row.as_dict() for row in self.items]
    self._mrp_calculation(mrp_lowest_llc)
    self._mrp_final_process()
    #self.reload()  #此方法在单独对象方法调用时需要

"""
from zelin_am.agile_manufacturing.doctype.multi_level_mrp.multi_level_mrp import *
self = frappe.get_doc({'doctype':'Multi Level MRP','company':'frappedemo','item_code':'计划成品'})
self = frappe.get_last_doc('Multi Level MRP')
self.initialize()
self._mrp_cleanup()
mrp_lowest_llc = self._low_level_code_calculation()
self.get_plan_items(save_db = True)
self._mrp_calculation(mrp_lowest_llc)
doc_list = self.get_mrp_inventory_data()
self._mrp_final_process()
self = frappe.new_doc('Multi Level MRP')
self.save()
self.run_mrp_multi_level()
"""    